﻿//-------------------------------------------------------------------------------------------------
// Copyright (c) Bradford W. Mott and Flare Contributors
// North Carolina State University, Department of Computer Science
// The IntelliMedia Group
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//-------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using UnityEngine;
using Flare.Display;
using Flare.Events;
using Flare.Script;

namespace Flare.Display
{
    public class TimelineEditorInfo
    {
        private SwfAsset currentAsset;
        private MovieClip clip;
        private List<TimelineScenes> sceneList = new List<TimelineScenes>();

        /// <summary>
        /// This gets the info necessary to generate the timeline
        /// </summary>
        public List<TimelineScenes> GetTimelineInfo(MovieClip clip)
        {
            this.clip = clip;

            for (int iteration = 0; iteration < this.clip.scenes.Length; iteration++)
            {
                sceneList.Add(new TimelineScenes(this.clip.scenes[iteration].name));
                sceneList[sceneList.Count - 1].framesInScene = this.clip.scenes[iteration].numFrames;

                foreach (FrameLabel label in this.clip.scenes[iteration].labels)
                {
                    sceneList[sceneList.Count - 1].AddFrame(label.frame, label.name);
                }

                for (int currentFrame = 0; currentFrame < sceneList[sceneList.Count - 1].framesInScene; currentFrame++)
                {
                    // gets what symbol was instantiated and what frame each symbol was instantiated and destroyed.
                    // Doing this before the loop because if at the last frame in the scene,
                    // calling clip.NextFrame() will go to the next scene. 
                    GetTimelineObjects(currentFrame);           
                    
                    // gets scripts
                    GetScripts(currentFrame);
                }
            }

            return this.sceneList;
        }

        public void GetSymbolInfo(Flare.Display.MovieClip symbol)
        {
            this.clip = symbol;
            for (int iteration = 0; iteration < symbol.scenes.Length; iteration++)
            {
                for (int currentFrame = 0; currentFrame < symbol.currentScene.numFrames; currentFrame++)
                {
                    GetTimelineObjects(currentFrame);
                    GetScripts(currentFrame);
                }
            }
        }

        /// <summary>
        /// In the current frame, gets any possible scripts and its contents.
        /// </summary>
        private void GetScripts(int currentFrame)
        {
            string[] scriptList = this.clip.GetScriptActions(currentFrame);

            if (scriptList != null)
            {
                sceneList[sceneList.Count - 1].AddScript(currentFrame + 1, scriptList);
            }
        }

        /// <summary>
        /// Gets the symbols created or deleted in the current frame.
        /// </summary>
        private void GetTimelineObjects(int currentFrame)
        {
            var currentData = this.clip.timeline.GetActionsAt(currentFrame);
            List<object> actionList = new List<object>();
            int firstDepth = -1;
            
            for (int a = 0; a < currentData.Count; a++)
            {
                int depth = -1;
                string name = "";

                MovieClip.TimelineAction action = currentData[a];

                if (action is MovieClip.PlaceObjectAction)
                {
                    MovieClip.PlaceObjectAction displayAction = (MovieClip.PlaceObjectAction)action;
                    depth = displayAction.depth;
                    name = displayAction.name;
                }
                
                if (depth != -1)
                {
                    actionList.Add(depth);
                }
                
                if (name != "")
                {
                    if (name == null)
                    {
                        actionList.Add("not named");
                    }
                    else
                    {
                        actionList.Add(name);
                    }
                }            
                
                if (currentData[a] is MovieClip.PlaceObjectAction)
                {
                    MovieClip.PlaceObjectAction placeObject = (MovieClip.PlaceObjectAction) currentData[a];
                    
                    actionList.Add(placeObject.placeFlagMove);
                }
            }

            // Go though all of the the strings
            for (int iteration  = 0; iteration < actionList.Count; iteration++)      
            {
                // create new timeline object with potential name
                if (actionList[iteration] is string)
                {
                    if (firstDepth != -1)
                    {
                        // create a new unnamed timeline object
                        if (actionList[iteration + 1].Equals(false))
                        {
                            sceneList[sceneList.Count - 1].AddSymbol((string)(actionList[iteration]), currentFrame + 1, firstDepth);
                            firstDepth = -1;
                        }
                    }  
                    else
                    {
                        Debug.LogError("A timeline object is not on a layer!");
                    }
                }
                // animating an existing timeline object
                else if (actionList[iteration] is bool)
                {
                    sceneList[sceneList.Count - 1].InsertSymbolAnimation(firstDepth, currentFrame);
                    firstDepth = -1;
                }
                // adding a new timeline object or deleting an already existing timeline object
                else if (actionList[iteration] is int)
                {
                    // delete timeline object
                    if (firstDepth != -1)
                    {
                        sceneList[sceneList.Count - 1].DeleteSymbol(firstDepth,currentFrame + 1);
                    }
                    firstDepth = (int)(actionList[iteration]);
                }
                else
                {
                    Debug.LogError("A timeline object hasn't been created correctly!");
                }
            }
        }
    }

    //------------------------------------------------------------------------------------------------------------------------------

    public class TimelineScenes
    {
        private string sceneName;
        
        /// <summary>
        /// The total number of frames within the current scene.
        /// </summary>
        private int sceneFrames;
        
        /// <summary>
        /// The total number of symbols within the current scene.
        /// </summary>
        private int sceneSymbols;
        
        /// <summary>
        /// The total number of scripts within the current scene.
        /// </summary>
        private int sceneScripts;
        
        /// <summary>
        /// The list of named frames in the current scene
        /// </summary>
        private List<TimelineFrames> frameList = new List<TimelineFrames> (); 
        
        /// <summary>
        /// The list of symbols used in the current scene
        /// </summary>
        private List<TimelineObjects> symbolList = new List<TimelineObjects> ();
        
        /// <summary>
        /// The list of scripts used in the current scene
        /// </summary>
        private List<TimelineScripts> scriptList = new List<TimelineScripts> ();
        
        /// <summary>
        /// The number of frames in the current scene.
        /// </summary>
        public int framesInScene
        {
            get { return this.sceneFrames;}
            set { this.sceneFrames = value;}
        }
        
        public int numberOfSymbols
        {
            get { return this.sceneSymbols;}
        }
        
        public int numberOfScripts
        {
            get { return this.sceneScripts;}
        }
        
        public string name
        {
            get { return this.sceneName;}
        }
        
        public TimelineScenes(string sceneName)
        {
            if (sceneName == "")
            {
                sceneName = "(not named)";
            }
            this.sceneName = sceneName;
        }
        
        /// <summary>
        /// Adds a new named frame.
        /// </summary>
        public void AddFrame(int frameNumber, string frameName)
        {
            frameList.Add(new TimelineFrames(frameNumber, frameName));
        }
        
        /// <summary>
        /// Adds a new symbol
        /// </summary>
        public void AddSymbol(string symbolName, int symbolFrame, int symbolDepth)
        {
            symbolList.Add(new TimelineObjects(symbolName, symbolFrame, symbolDepth, sceneFrames));
            sceneSymbols++;
        }
        
        public void AddScript(int frameNumber, string[] contents)
        {
            scriptList.Add(new TimelineScripts(frameNumber, contents));
            sceneScripts++;
        }
        
        public void DeleteSymbol(int symbolDepth, int frameSymbolDeleted)
        {
            int location = 0;
            
            // find the symbol using the symbol depth
            for (int iteration = 0; iteration < symbolList.Count; iteration++)
            {
                if (symbolList[iteration].depth.Equals(symbolDepth))
                {
                    location = iteration;
                    break;
                }
            }
            symbolList[location].FrameDeletedSymbol(frameSymbolDeleted);
        }
        
        public string[] scriptContents(int currentFrame)
        {
            return scriptList[currentFrame].scriptContents;
        }
        
        public string[] GetFrameNames()
        {
            string[] list = new string[this.frameList.Count];
            int location = 0;
            foreach (TimelineFrames frame in this.frameList)
            {
                list[location] = frame.name;
                location++;
            }
            return list;
        }
        
        public int[] GetFrameNumbers()
        {
            int[] list = new int[frameList.Count];
            int location = 0;
            foreach (TimelineFrames frame in frameList)
            {
                list[location] = frame.number;
                location++;
            }
            return list;
        }
        
        public string[] GetSymbolNames()
        {
            string[] list = new string[symbolList.Count];
            int location = 0;
            foreach (TimelineObjects symbol in symbolList)
            {
                list[location] = symbol.name;
                location++;
            }
            return list;
        }
        
        public int[] GetSymbolCreatedFrame()
        {
            int[] list = new int[symbolList.Count];
            int location = 0;
            foreach (TimelineObjects symbol in symbolList)
            {
                list[location] = symbol.createdFrame;
                location++;
            }
            return list;
        }
        
        public int[] GetSymbolDeletedFrame()
        {
            int[] list = new int[symbolList.Count];
            int location = 0;
            foreach (TimelineObjects symbol in symbolList)
            {
                list[location] = symbol.deletedFrame;
                location++;
            }
            return list;
        }
        
        public int[] GetScriptFrames()
        {
            int[] list = new int[scriptList.Count];
            int location = 0;
            foreach (TimelineScripts script in scriptList)
            {
                list[location] = script.frame;
                location++;
            }
            return list;
        }
        
        public void InsertSymbolAnimation(int depth, int currentFrame)
        {
            for (int iteration = 0; iteration < symbolList.Count; iteration++)
            {
                // Find the correct location
                if (symbolList[iteration].depth == depth)
                {
                    // indication of first animation frame
                    if (symbolList[iteration].startAnimation  == 0)
                    {
                        symbolList[iteration].startAnimation = currentFrame;;
                    }
                    // indication of last animation frame
                    else if (symbolList[iteration].endAnimation == 0)
                    {
                        symbolList[iteration].AddAnimationFrames(symbolList[iteration].startAnimation, currentFrame);
                        symbolList[iteration].endAnimation = currentFrame;
                    }
                    
                    else if (symbolList[iteration].endAnimation + 1 == currentFrame)
                    {
                        symbolList[iteration].RemoveAnimationFrames(symbolList[iteration].startAnimation);
                        symbolList[iteration].AddAnimationFrames(symbolList[iteration].startAnimation, currentFrame);
                        symbolList[iteration].endAnimation = currentFrame;
                    }
                    else
                    {
                        symbolList[iteration].startAnimation = currentFrame;
                        symbolList[iteration].endAnimation = 0;
                    }
                }
            }
        }
        
        public int[] GetAnimations()
        {
            int location = 0;
            
            foreach (TimelineObjects symbol in symbolList)
            {
                location += symbol.framesAnimated.Keys.Count * 2 + 1;
            }
            int[] animationList = new int[location + 2];
            
            location = 0;
            foreach (TimelineObjects symbol in symbolList)
            {
                foreach (KeyValuePair<int, int> key in symbol.framesAnimated)
                {
                    animationList[location] = key.Key;
                    animationList[location + 1] = key.Value;
                    location += 2;
                }
                
                if (animationList.Length > location)
                {
                    animationList[location] = 0;
                    location++;
                }
            }
            animationList[location + 1] = 0;
            return animationList;
        }
       
        //---------------------------------------------------------------------------------------
        
        /// <summary>
        /// Container for symbols used in the SWF file.
        /// </summary>
        public class TimelineObjects
        {
            private string symbolName;
            private int frameCreated;
            private int frameDeleted;
            private int symbolDepth;
            private int sAnimation = 0;
            private int eAnimation = 0;
            
            // The key is the frame of the start of the animation
            // and the value is the frame of the last animation
            private Dictionary<int, int> animatedFrames = new Dictionary<int, int>();
            
            /// <summary>
            /// Creates a new symbol
            /// </summary>
            public TimelineObjects(string symbolName, int frameCreated, int symbolDepth, int sceneFrames)
            {
                this.symbolName = symbolName;
                this.frameCreated = frameCreated;
                this.symbolDepth = symbolDepth;
                this.frameDeleted = sceneFrames;
            }
            
            /// <summary>
            /// The name of the symbol in question
            /// </summary>
            public string name
            {
                get { return this.symbolName;}
            }
            
            /// <summary>
            /// The frame that the symbol was created.
            /// </summary>
            public int createdFrame
            {
                get { return this.frameCreated;}
            }
            
            /// <summary>
            /// The frame that the symbol was deleted.
            /// </summary>
            public int deletedFrame
            {
                get { return this.frameDeleted;}
            }
            
            /// <summary>
            /// The symbol depth. The Depth is the layer in the SWF file that the symbol is located.
            /// </summary>
            public int depth
            {
                get { return this.symbolDepth;}
                set { this.symbolDepth = value;}
            }
            
            /// <summary>
            /// The list of frames that the current symbol has animation
            /// </summary>
            public Dictionary<int, int> framesAnimated
            {
                get { return this.animatedFrames;}
            }
            
            /// <summary>
            /// The start frame for the most recent animation sequence (if any).
            /// </summary>
            public int startAnimation
            {
                get { return this.sAnimation;}
                set { this.sAnimation = value;}
            }
            
            /// <summary>
            /// The end frame for the most recent animation sequence (if any).
            /// </summary>
            public int endAnimation
            {
                get { return this.eAnimation;}
                set { this.eAnimation = value;}
            }
            
            /// <summary>
            /// Adds an animation sequence.
            /// </summary>
            public void AddAnimationFrames(int startFrame, int endFrame)
            {
                animatedFrames.Add(startFrame, endFrame);
            }
            
            /// <summary>
            /// Removes an animation sequence with given the start frame.
            /// </summary>
            public void RemoveAnimationFrames(int startFrame)
            {
                animatedFrames.Remove(startFrame);
            }
            
            /// <summary>
            /// Defines which frame the symbol was deleted
            /// </summary>
            public void FrameDeletedSymbol(int frameDeleted)
            {
                this.frameDeleted = frameDeleted;
            }
        }
        
        //---------------------------------------------------------------------------------------
        
        /// <summary>
        /// Container for the named frames in the SWF file.
        /// </summary>
        public class TimelineFrames
        {
            private string frameName;
            private int frameNumber;
            
            /// <summary>
            /// The name of the frame in question
            /// </summary>
            public string name
            {
                get { return frameName; }
            }
            
            /// <summary>
            /// The frame number that the frame name is associated with
            /// </summary>
            public int number
            {
                get { return frameNumber;}
            }
            
            public TimelineFrames (int frameNumber, string frameName)
            {
                this.frameName = frameName;
                this.frameNumber = frameNumber;
            }       
        }
        
        //---------------------------------------------------------------------------------------
        
        /// <summary>
        /// Container for the AS2 scripts in the SWF file.
        /// </summary>
        public class TimelineScripts
        {
            private int frameInitiated;
            private string[] contents;
            
            /// <summary>
            /// The frame number that the script is on.
            /// </summary>
            public int frame
            {
                get { return this.frameInitiated;} 
            }
            
            /// <summary>
            /// The contents within the script. Each line of code in the
            /// SWF file is its own index within this variable.
            /// </summary>
            public string[] scriptContents
            {
                get { return this.contents;}
            }
            
            public TimelineScripts (int frameInitiated, string[] contents)
            {
                this.frameInitiated = frameInitiated;
                this.contents = contents;
            }
        }
    }
}